﻿using Gargoyle.Roton;
using System;
using System.Collections.Generic;
using System.Text;

namespace Gargoyle.ZZT
{
    public delegate void CommandDelegate(Game g, OOPState oop, Pointer instruction);

	/// <summary>
	/// Contains code for OOP commands.
	/// </summary>
    static class Commands
    {
		/// <summary>
		/// Code for #BECOME.
		/// </summary>
        public static void Become(Game g, OOPState oop, Pointer instruction)
        {
            Tile kind = new Tile(0, 0);
            if (g.ReadKind(oop.Thing, instruction, ref kind))
            {
                oop.Died = true;
                oop.Tile = kind;
            }
            else
            {
                g.RaiseOOPError(oop.Thing, "Bad #BECOME");
            }
        }

		/// <summary>
		/// Code for #BIND.
		/// </summary>
		public static void Bind(Game g, OOPState oop, Pointer instruction)
        {
            int searchIndex = 0;

            g.ReadWord(oop.Thing, instruction);
            if (g.FindTarget(oop.Index, ref searchIndex, g.OOPWord))
            {
                //oop.Thing.Code = g.Things[searchIndex].Code;
                oop.Thing.Pointer = g.Things[searchIndex].Pointer;
                oop.Thing.Length = g.Things[searchIndex].Length;
                instruction.Int16 = 0;
            }
        }

		/// <summary>
		/// Code for #CHANGE.
		/// </summary>
		public static void Change(Game g, OOPState oop, Pointer instruction)
        {
            Tile source = new Tile();
            Tile target = new Tile();
            Location location = new Location(0, 0);
            bool success = false;

            if (g.ReadKind(oop.Thing, instruction, ref source))
            {
                if (g.ReadKind(oop.Thing, instruction, ref target))
                {
                    success = true;
                    if (target.Color == 0 && g.Elements[target.Element].DefaultColor < 0xF0)
                    {
                        target.Color = g.Elements[target.Element].DefaultColor;
                    }
                    while (g.FindTile(source, location))
                    {
                        g.PlotTile(location, target);
                    }
                }
            }

            if (!success)
            {
                g.RaiseOOPError(oop.Thing, "Bad #CHANGE");
            }

        }

		/// <summary>
		/// Code for #CHAR.
		/// </summary>
		public static void Char(Game g, OOPState oop, Pointer instruction)
        {
            g.ReadNumber(oop.Thing, instruction);
            if (g.OOPNumber >= 0)
            {
                oop.Thing.P1 = (byte)(g.OOPNumber);
                g.UpdateAt(oop.Thing.Location);
            }
        }

		/// <summary>
		/// Code for #CLEAR.
		/// </summary>
		public static void Clear(Game g, OOPState oop, Pointer instruction)
        {
            g.ReadWord(oop.Thing, instruction);
            g.ClearFlag(g.OOPWord);
        }

		/// <summary>
		/// Code for #CYCLE.
		/// </summary>
		public static void Cycle(Game g, OOPState oop, Pointer instruction)
        {
            g.ReadNumber(oop.Thing, instruction);
            if (g.OOPNumber > 0)
            {
                oop.Thing.Cycle = (Int16)g.OOPNumber;
            }
        }

		/// <summary>
		/// Code for #DIE.
		/// </summary>
		public static void Die(Game g, OOPState oop, Pointer instruction)
        {
            oop.Died = true;
            oop.Tile = new Tile(g.Elements.IndexOf(ElementID.Empty), 0x0F);
        }

		/// <summary>
		/// Code for #END.
		/// </summary>
		public static void End(Game g, OOPState oop, Pointer instruction)
        {
            // we need to set the OOPbyte for this command.
			// it's uncharacteristic for an OOP command but the original executable does this.
            g.State.OOPByte = 0;
            instruction.Int16 = -1;
        }

		/// <summary>
		/// Code for #ENDGAME.
		/// </summary>
		public static void EndGame(Game g, OOPState oop, Pointer instruction)
        {
            g.Info.Health = 0;
        }

		/// <summary>
		/// Executes a transaction for an item type.
		/// </summary>
		/// <param name="isTake">If true, this is for a #TAKE command.</param>
		private static bool ExecuteTransaction(Game g, OOPState oop, Pointer instruction, bool isTake)
        {
            bool result = true;
            ItemDelegate item = null;

            if (g.ReadItem(oop.Thing, instruction, ref item))
            {
                if (g.OOPNumber > 0)
                {
                    if (isTake)
                    {
                        // OOPNumber IS manipulated directly
                        g.State.OOPNumber = -g.State.OOPNumber;
                    }
                    result = item(g, g.OOPNumber);
                }
            }
            g.UpdateStatus();

            return result;
        }

		/// <summary>
		/// Code for #GIVE.
		/// </summary>
		public static void Give(Game g, OOPState oop, Pointer instruction)
        {
            oop.Resume = !ExecuteTransaction(g, oop, instruction, false);
        }

		/// <summary>
		/// Code for #GO.
		/// </summary>
		public static void Go(Game g, OOPState oop, Pointer instruction)
        {
            Vector vector = new Vector();
            bool success = g.ReadDirection(oop.Thing, instruction, vector);
            Location target = oop.Thing.Location.Add(vector);

            if (success)
            {
                if (!g.GetElementAt(target).Floor)
                {
                    g.Push(target, vector);
                }
                if (g.GetElementAt(target).Floor)
                {
                    g.MoveThing(oop.Index, target);
                    oop.Moved = true;
                }
                else
                {
                    oop.Repeat = true;
                }
            }
        }

		/// <summary>
		/// Code for #IDLE.
		/// </summary>
		public static void Idle(Game g, OOPState oop, Pointer instruction)
        {
            oop.Moved = true;
        }

		/// <summary>
		/// Code for #IF.
		/// </summary>
		public static void If(Game g, OOPState oop, Pointer instruction)
        {
            bool result = false;
            if (g.ReadCondition(oop.Thing, instruction, ref result))
            {
                oop.Resume = result;
            }
        }

		/// <summary>
		/// Code for #LOCK.
		/// </summary>
		public static void Lock(Game g, OOPState oop, Pointer instruction)
        {
            if (g.Parameters.MultiMovement)
                oop.Thing.P3 = 1;
            else
                oop.Thing.P2 = 1;
        }

		/// <summary>
		/// Code for #PLAY.
		/// </summary>
		public static void Play(Game g, OOPState oop, Pointer instruction)
        {
            g.PlayMusic(-1, g.ReadLine(oop.Thing, instruction));
            oop.NextLine = false;
        }

		/// <summary>
		/// Code for #PUT.
		/// </summary>
		public static void Put(Game g, OOPState oop, Pointer instruction)
        {
            int bottom = g.Size.Height - 1;
            int right = g.Size.Width - 1;
            Vector vector = new Vector();
            bool success = false;

            if (g.Parameters.BottomRowPutBug)
            {
                bottom--;
            }

            g.ReadDirection(oop.Thing, instruction, vector);
            if (!vector.IsIdle)
            {
                Tile tile = new Tile();
                if (g.ReadKind(oop.Thing, instruction, ref tile))
                {
                    Location target = oop.Thing.Location.Add(vector);
                    if (target.X > 0 && target.X < right && target.Y > 0 && target.Y < bottom)
                    {
                        if (!g.GetElementAt(target).Floor)
                        {
                            g.Push(target, vector);
                        }
                        g.PlotTile(target, tile);
                    }
                    success = true;
                }
            }

            if (!success)
            {
                g.RaiseOOPError(oop.Thing, "Bad #PUT");
            }
        }

		/// <summary>
		/// Code for #RESTART.
		/// </summary>
		public static void Restart(Game g, OOPState oop, Pointer instruction)
        {
            instruction.Int16 = 0;
            oop.NextLine = false;
        }

		/// <summary>
		/// Code for #RESTORE.
		/// </summary>
		public static void Restore(Game g, OOPState oop, Pointer instruction)
        {
            int searchIndex = 0;
            Pointer searchOffset = new Pointer(new byte[2]);
            ByteString prefix = new byte[] { 0x0D, 0x27 }; // '

            g.ReadWord(oop.Thing, instruction);
            while (g.FindLabel(oop.Index, g.OOPWord, ref searchIndex, searchOffset, prefix))
            {
                do
                {
                    g.Things[searchIndex].Code[searchOffset.Int16 + 1] = 0x3A; // :
                    searchOffset.Int16 = g.SearchOOP(searchIndex, prefix + g.OOPWord);
                } while (searchOffset.Int16 > 0);
            }
        }

		/// <summary>
		/// Code for #SEND.
		/// </summary>
		public static void Send(Game g, OOPState oop, Pointer instruction)
        {
            g.ReadWord(oop.Thing, instruction);
            oop.NextLine = (!g.SendLabel(oop.Index, g.OOPWord, false));
        }

		/// <summary>
		/// Code for #SET.
		/// </summary>
		public static void Set(Game g, OOPState oop, Pointer instruction)
        {
            g.ReadWord(oop.Thing, instruction);
            g.SetFlag(g.OOPWord);
        }

		/// <summary>
		/// Code for #SHOOT.
		/// </summary>
		public static void Shoot(Game g, OOPState oop, Pointer instruction)
        {
            Vector vector = new Vector();
            bool success = g.ReadDirection(oop.Thing, instruction, vector);

            if (success)
            {
                if (g.SpawnProjectile(g.Elements[ElementID.Bullet], oop.Thing.Location, vector, true))
                {
                    g.PlaySound(2, g.Sounds.EnemyShoot);
                }
                oop.Moved = true;
            }
        }

		/// <summary>
		/// Code for #TAKE.
		/// </summary>
		public static void Take(Game g, OOPState oop, Pointer instruction)
        {
            oop.Resume = !ExecuteTransaction(g, oop, instruction, true);
        }

		/// <summary>
		/// Code for #THEN.
		/// </summary>
		public static void Then(Game g, OOPState oop, Pointer instruction)
        {
            oop.Executed = false;
            oop.NextLine = false;
        }

		/// <summary>
		/// Code for #THROWSTAR.
		/// </summary>
		public static void ThrowStar(Game g, OOPState oop, Pointer instruction)
        {
            Vector vector = new Vector();
            bool success = g.ReadDirection(oop.Thing, instruction, vector);

            g.SpawnProjectile(g.Elements[ElementID.Star], oop.Thing.Location, vector, true);
            oop.Moved = true;
        }

		/// <summary>
		/// Code for #TRY.
		/// </summary>
		public static void Try(Game g, OOPState oop, Pointer instruction)
        {
            Vector vector = new Vector();
            bool success = g.ReadDirection(oop.Thing, instruction, vector);
            Location target = oop.Thing.Location.Add(vector);

            if (success)
            {
                if (!g.GetElementAt(target).Floor)
                {
                    g.Push(target, vector);
                }
                if (g.GetElementAt(target).Floor)
                {
                    g.MoveThing(oop.Index, target);
                    oop.Moved = true;
                    oop.Resume = false;
                }
                else
                {
                    oop.Resume = true;
                }
            }
        }

		/// <summary>
		/// Code for #UNLOCK.
		/// </summary>
		public static void Unlock(Game g, OOPState oop, Pointer instruction)
        {
            if (g.Parameters.MultiMovement)
                oop.Thing.P3 = 0;
            else
                oop.Thing.P2 = 0;
        }

		/// <summary>
		/// Code for #WALK.
		/// </summary>
		public static void Walk(Game g, OOPState oop, Pointer instruction)
        {
            Vector vector = new Vector();
            bool success = g.ReadDirection(oop.Thing, instruction, vector);

            if (success)
            {
                oop.Thing.Vector.CopyFrom(vector);
            }
        }

		/// <summary>
		/// Code for #ZAP.
		/// </summary>
		public static void Zap(Game g, OOPState oop, Pointer instruction)
        {
            int searchIndex = 0;
            Pointer searchOffset = new Pointer(new byte[2]);
            ByteString prefix = new byte[] { 0x0D, 0x3A }; // :

            g.ReadWord(oop.Thing, instruction);
            while (g.FindLabel(oop.Index, g.OOPWord, ref searchIndex, searchOffset, prefix))
            {
                g.Things[searchIndex].Code[searchOffset.Int16 + 1] = 0x27; // '
            }
        }
    }
}
